package pt.minha.disk.simulation;

import java.util.Collections;
import java.util.LinkedList;

public class Storage implements Runnable{

	/* Global */
	private final long seekTime;
	private final int diskDebit; //per milisecond
	private boolean shutdown = false;
	/* --- */
	
	/*Write info*/
	private final int bucketSize;
	private int bucketFreeSize;
	private final long writeTimePerByte; //Time for write 1 byte
	private final long writeFixeTime;
	
	private LinkedList<Request> bucket;
	private LinkedList<Request> queue;
	/* ---------- */
	
	/*Read info*/
	private final long readTimePerByte; //Time for read 1 byte
	private final long readFixeTime;
	/* ---------- */
	
	/***
	 * Main constructor
	 * @param bucketSize
	 * @param seekTime
	 * @param writeTimePerByte
	 * @param writeFixeTime
	 * @param readTimePerByte
	 * @param readFixeTime
	 * @param diskDebit
	 */
	public Storage(int bucketSize, long seekTime, long writeTimePerByte, long writeFixeTime, long readTimePerByte, long readFixeTime, int diskDebit) {
		this.diskDebit = diskDebit;
		this.writeTimePerByte = writeTimePerByte;
		this.writeFixeTime = writeFixeTime;
		this.readTimePerByte = readTimePerByte;
		this.readFixeTime = readFixeTime;
		this.seekTime = seekTime;
		this.bucketSize = bucketSize;
		this.bucketFreeSize = this.bucketSize;
		this.bucket = (LinkedList<Request>) Collections.synchronizedList(new LinkedList<Request>());
		this.queue = (LinkedList<Request>) Collections.synchronizedList(new LinkedList<Request>());
	}
	
	/***
	 * Function for adding a new request to a disk and delegate it
	 * @param r request
	 */
	public synchronized void addRequest(Request r) {
		if(r.getReqType().equals(RequestType.READ)) this.handleReads(r);

		if(r.getReqType().equals(RequestType.WRITE)) this.handleWrites(r);
	}
	
	/***
	 * Function responsible for handling read request
	 * @param r - request
	 */
	private void handleReads(Request r){
		long cost = this.readFixeTime + (r.getLength() * this.readTimePerByte);
		
		r.done(cost);
	}
	
	/***
	 * Function responsible for handling write request
	 * @param r - request
	 */
	private void handleWrites(Request r){
		if((r.getLength() > bucketFreeSize || !this.queue.isEmpty())){
			//if request length is greater then bucket free size or if is any request in queue
			this.addQueue(r);
			return;
		}
		
		this.addBucket(r);
	}
	
	/***
	 * Function for adding the request to the bucket
	 * @param r request
	 */
	private synchronized void addBucket(Request r) {
		this.bucketFreeSize -= r.getLength();

		long cost = this.writeFixeTime + (this.writeTimePerByte * r.getLength());
		
		r.done(cost);
		
		this.bucket.add(r);
	}

	/***
	 * Function for adding the request to the queue
	 * @param r request
	 */
	private synchronized void addQueue(Request r) {
		this.addTimeStamp(r);
		this.queue.add(r);
	}

	/***
	 * Function for adding the timestamp in request
	 * @param r request
	 */
	private void addTimeStamp(Request r) {
		r.setTimeStamp(System.nanoTime());
	}
	
	/***
	 * Function responsible for free a bucket size
	 * @param size - space to be freed
	 */
	private synchronized void freeBucket(int size){
		this.bucketFreeSize += size;
		
		Request r = this.queue.getFirst();
		
		if(r.getLength() <= this.bucketFreeSize){
			this.addBucket(r);
			this.queue.removeFirst();
		}
	}
	
	/***
	 * Function that manages the process of processing orders
	 */
	private void processPack(){
		Request r = this.bucket.getFirst();
		
		//FIXME: stop running disk
		
		this.freeBucket(r.process(this.diskDebit));
		
		if(r.isDone()) this.bucket.removeFirst();
	}
	
	/***
	 * Function responsible for stop a thread some time
	 * @param nano - time in nanoseconds
	 */
	private synchronized void waiting(long nano){
		try{
			this.wait(nano);
		}catch(Exception e){
			
		}
	}

	@Override
	public void run() {
		
		while(!this.shutdown){
			this.processPack();
			this.waiting(1000);
		}
		
	}

}
